package BNS.graphs;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.Scanner;
import java.util.StringTokenizer;

import javax.swing.JFrame;

import BNS.plumbing.Library;


/** an extension to the Graph class that provides a way of specifying a Graph
	by reading an adjacency matrix from a file. 
	*/
public class MatrixGraph extends AnnotatedGraph {
	
	/**
   * 
   */
  private static final long serialVersionUID = 1L;
  public final String params, comment;
	public final int chromaticNumber, diameter, dominationRadius;
	public final double clusterQuotient;
	public final java.util.Date creationDate;
	/** Returns a String containing human-readable data pertaining to this graph. */
	public String getInfo() { 
		return params +" "+ comment +"\n"
		+" chromaticNumber="+ chromaticNumber
		+" diameter="+ diameter
		+" date= "+ creationDate;
		}
	
	/** Driver to create graphs, but does not store them. 
		Merely prints them on System.err, for testing this class. */
	public static void main(String[] args) throws IOException {
		System.err.println("MatrixGraph "+ args.length);
		Graph gr= null;
		if (args.length==0) gr= create();
		else gr= new MatrixGraph(new File(args[0]));
		if (gr!=null) {
			if(gr.numNodes()<20) gr.prettyPrint(System.err);
			else gr.histoDegrees(System.err);
			}
		else System.err.println("null graph");
		}
	
	/** Returns the results of create(). Probably obsolete.
		@param frame ignored.
		*/
	public static MatrixGraph create(JFrame frame) { return create(); }
	
	/** Obtains an adjacency matrix by prompting the user to select a file,
		and returns a Graph constructed from it.
		*/
	public static MatrixGraph create() {
		try {
			File file= Library.getSelectedFile();
			if (file!=null) return new MatrixGraph(file);	}
		catch(IOException e) { System.err.println(e); }
		return null;
		}
	/** Creates and returns a Graph from an adjacency matrix.
		Prints a message on System.err if an exception occurs.
		@param file the File where the matrix is stored. 
		*/
	public static MatrixGraph create(File file) {
		try { return new MatrixGraph(file);	}
		catch(IOException e) { System.err.println(e); }
		return null;
		}
	
	/** Creates a graph using a loop-free symmetric adjacency matrix as input.
		Each entry must be an int; non-zero entries will indicate an edge.
		The number of entries on the FIRST LINE of input will be taken as the # of nodes.
		Subsequent LINES must have the same number of entries on them.
		The "top right" of the matrix (above the diagonal) is definitive; other
		entries are not checked for compliance with the symmetry or no-loop conditions.
		@param file the text file containing the adjacency matrix 
		*/
	public MatrixGraph(File file) throws IOException {
		super(10,10);
		System.err.println("generating MatrixGraph from "+ file);
		creationDate= new Date();
		this.params= file.toString();
		this.comment= "from adjacency matrix";
		FileReader frdr= new FileReader(file);
		BufferedReader rdr= new BufferedReader(frdr);
		
		int[] nmbrs= readLine(rdr);
		int size= nmbrs.length;
		Node[] nod= new Node[size];
		System.err.println("MatrixGraph has size "+ size);
		//for(int t=0; t<size; t++) System.err.println(nmbrs[t]+": "+t);
//		Graph gr= new Graph(size,6*size);
		
		ArrayList<String> diagonal = getDiagonal (file);
		ArrayList<Character> nodeClasses = parseClasses (diagonal);
		ArrayList<Integer> nodeSQColors = parseSQColors (diagonal);
		System.err.println ("node classes: " + nodeClasses);
		System.err.println ("node sqcolors: " + nodeSQColors);
		
		// Create node, assign it a title, class, and status quo color.
		for(int t=0; t<size; t++)
			nod[t]= addNode("n"+(t+1), nodeClasses.get(t), nodeSQColors.get(t));
		
		for(int t=0; t<size-1; t++) {	// matrix assumed to be symmetric
			for(int a=t+1; a<size; a++)	// use only the top right half of matrix
				if (nmbrs[a]!=0)
					addEdge(nod[t],nod[a]);
			nmbrs= readLine(rdr);
			}
		System.err.println("MatrixGraph generated graph");
		chromaticNumber= 0;			// TODO: make this meaningful
		diameter= 0;				// TODO: make this meaningful
		dominationRadius= 0;		// TODO: make this meaningful
		clusterQuotient= 0.;		// TODO: make this meaningful		
		}
	
	/** Reads a line from a given BufferedReader, and converts it to an array
		of zeros and ones by testing to see if the first char of each token is a 0.
		If the first char is a letter (upper or lowercase), then it counts as a 0. That letter is
		the node's "class", which is read and parsed by the GameBuilder.
		*/
	private static int[] readLine(BufferedReader rdr) {
		String line= null;
		try { line= rdr.readLine(); }
		catch(Exception e) { System.err.println(e); }
		StringTokenizer st= new StringTokenizer(line);
		int i=0, size= st.countTokens();
		int[] nmbrs= new int[size];
		// TODO: Modify the system if necessary. Using the diagonal as a place to store the node's
		// "class," so we can define different payoffs for different classes. The diagonal also
		// stores the starting color of the node (0 is blank).
		while (st.hasMoreTokens()) {
			String token = st.nextToken();
			if ((token.charAt(0) >= 'a' && token.charAt(0) <= 'z') ||
				(token.charAt(0) >= 'A' && token.charAt(0) <= 'Z')) {
				nmbrs[i++] = token.charAt(0); 
			}
			else
				nmbrs[i++]= token.charAt(0)=='0'? 0:1;
		}
		return nmbrs;
		}
	
	/**
	 * Returns an ArrayList of Strings, where the ith entry is the String that was in the diagonal
	 * position of the ith node in the matrix file (the input parameter).
	 * 
	 * @param 	The matrix file.
	 * @return	An ArrayList of Strings that holds the diagonal entries of the matrix file.
	 */
	private ArrayList<String> getDiagonal (File file)
	{
		ArrayList<String> diagonal = new ArrayList<String>();
		try {
			Scanner scan = new Scanner (file);
			int nodeNum = 0;
			while (scan.hasNextLine()) {
				String currentLine = scan.nextLine();
				Scanner scanLine = new Scanner (currentLine);
				for (int i=0; i<nodeNum; i++) {
					scanLine.next();
				}
				diagonal.add (scanLine.next());
				nodeNum++;
			}
			
			return diagonal;
		}
		catch (FileNotFoundException e) {
			e.printStackTrace();
		}
		return null;
	}
	
	/**
	 * Given an array of the diagonal values of the matrix file, pulls apart the node classes. If no
	 * class is assigned, then it is assigned to the default class (represented by '?').
	 * 
	 * @param diagonal	The values along the diagonal of the matrix file.
	 * @return			The classes each node is in.
	 */
	private ArrayList<Character> parseClasses (ArrayList<String> diagonal)
	{
		ArrayList<Character> nodeClasses = new ArrayList<Character>();
		
		for (int i=0; i<diagonal.size(); i++) {
			char currentClass = '?'; // Default value is null.
			String nodeString = diagonal.get(i);
			
			for (int j=0; j<nodeString.length(); j++) {
				char currentChar = nodeString.charAt(j);
				if ((currentChar >= 'A' && currentChar <= 'Z') ||
					(currentChar >= 'a' && currentChar <= 'z')) {
					currentClass = currentChar;
					break;
				}
			}
			
			nodeClasses.add (currentClass);
		}
		
		return nodeClasses;
	}
	
	/**
	 * Given an array of the diagonal values of the matrix file, pulls apart the node's status quo
	 * color. If no color is assigned, then it is assigned to the default color (0 = blank).
	 * 
	 * @param diagonal	The values along the diagonal of the matrix file.
	 * @return			An array of each node's status quo color.
	 */
	private ArrayList<Integer> parseSQColors (ArrayList<String> diagonal)
	{
		ArrayList<Integer> sqColors = new ArrayList<Integer>();
		
		for (int i=0; i<diagonal.size(); i++) {
			int currentColor = 0; // Default color is blank.
			String nodeString = diagonal.get(i);
			
			for (int j=0; j<nodeString.length(); j++) {
				char currentChar = nodeString.charAt(j);
				// Assume adjacent digits means a multi-digit number. 0123
				if ((currentChar >= '0' && currentChar <= '9')) {
					currentColor *= 10;
					currentColor += currentChar-'0';
				}
			}
			
			sqColors.add (currentColor);
		}
		
		return sqColors;
	}
}
